﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using OpenTK;
using OpenTK.Graphics.OpenGL;

//TODO: switch to exceptions and remove error codes

namespace Platform359a.Core.Gfx
{
    public class ShaderManager
    {
        private const int GL_TRUE = 1;
        //uniforms for each shader.  key is program handle, value is a dictionary whos key is uniform string name, value is uniform handle
        private int mCurrentProgram;
        Dictionary<string, int> mShaderLibrary;  //uint identifies program
        Dictionary<int,Dictionary<string,int>> mShaderUniforms;

        public static ShaderManager ActiveManager { get; private set; }
        
        public ShaderManager()
        {
            mShaderLibrary = new Dictionary<string, int>();
            mShaderUniforms = new Dictionary<int, Dictionary<string, int>>();
        }
        
        public void MakeActiveManager()
        {
            ActiveManager = this;
        }

        public void AddProgram(string pShaderProgramName,string pVertexShaderLocation,string pFragmentShaderLocation)
        {
            //error check input values
            if (ShaderExists(pShaderProgramName))
            {
                //! error shader already exists!
            }
            if (!System.IO.File.Exists(@pVertexShaderLocation))
            {
                //! error file not found
            }
            if (!System.IO.File.Exists(@pFragmentShaderLocation))
            {
                //! error file not found
            }

            //1. Create Shader
            //2. Attach Shader Object
            //3. Link Shader Program
            //4. Verify Link Successful
            //5. Store in Dictionary

            int vertexShader, fragmentShader;

            vertexShader = GL.CreateShader(ShaderType.VertexShader);
            fragmentShader = GL.CreateShader(ShaderType.FragmentShader);
 
            GL.ShaderSource(vertexShader, @pVertexShaderLocation);
            GL.ShaderSource(fragmentShader, @pFragmentShaderLocation);

            GL.CompileShader(vertexShader);
            GL.CompileShader(fragmentShader);

            int compileStatus;

            GL.GetShader(vertexShader, ShaderParameter.CompileStatus,out compileStatus);

            if (compileStatus != GL_TRUE)
            {
                //! compile error
            }

            GL.GetShader(fragmentShader, ShaderParameter.CompileStatus, out compileStatus);

            if (compileStatus != GL_TRUE)
            {
                //! compile error
            }

            int newProgram = GL.CreateProgram();

            GL.AttachShader(newProgram, vertexShader);
            GL.AttachShader(newProgram, fragmentShader);

            GL.LinkProgram(newProgram);

            int linkStatus;

            GL.GetProgram(newProgram, ProgramParameter.LinkStatus, out linkStatus);

            if (linkStatus != GL_TRUE)
            {
                //! linking error
            }

            GL.DetachShader(newProgram, vertexShader);
            GL.DetachShader(newProgram, fragmentShader);

            GL.DeleteShader(vertexShader);
            GL.DeleteShader(fragmentShader);

            mShaderLibrary.Add(pShaderProgramName, newProgram);
            mShaderUniforms.Add(newProgram, new Dictionary<string, int>());
            
        }

        public void ChangeProgram(string pShaderProgramName)
        {
            if (!ShaderExists(pShaderProgramName) || !GL.IsProgram(mShaderLibrary[pShaderProgramName]))
            {
                //! error shader does not exist or item is not a program
            }

            mCurrentProgram = mShaderLibrary[pShaderProgramName];
     
        }  

        public int UseProgram()
        {
            if(mCurrentProgram <= 0)
            {
                return 1;
            }

            GL.UseProgram(mCurrentProgram);
            return 0;
        }

        public int DeleteProgram(string pShaderProgramName)
        {
            if (!ShaderExists(pShaderProgramName))
            {
                return 1;
            }

            GL.DeleteProgram(mShaderLibrary[pShaderProgramName]);

            //check status to see if program was actually deleted
            if (!GL.IsProgram(mShaderLibrary[pShaderProgramName]))  
            {
                mShaderLibrary.Remove(pShaderProgramName);
                return 0;
            }
            else
            {
                return 2;
            }
        }
        public bool ShaderExists(string pShaderProgramName)
        {
            return mShaderLibrary.ContainsKey(pShaderProgramName);
        }

        public int AddUniform(string pUniformName,string pVariableName)
        {
            if (mShaderUniforms[mCurrentProgram].ContainsKey(pUniformName))
            {
                return 2;
            }
            if (mCurrentProgram <= 0)
            {
                return 3;
            }

            int uniformLocation = GL.GetUniformLocation(mCurrentProgram, pVariableName);
            if (uniformLocation < 0)
            {
                return 1;
            }

            mShaderUniforms[mCurrentProgram].Add(pUniformName, uniformLocation);
            return 0;
        }

        public int GetUniform(string pUniformName)
        {
            if (mCurrentProgram <= 0)
            {
                return -1;
            }
            if (!mShaderUniforms[mCurrentProgram].ContainsKey(pUniformName))
            {
                return -2;
            }

            return mShaderUniforms[mCurrentProgram][pUniformName]; 
        }

    }
}
